﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml;
using System.Reflection;
using intelliEssay.Interop.FontServices;

namespace intelliEssay.Interop
{
    /// <summary>
    /// 提供关于段落居中的情况
    /// </summary>
    public enum ParagraphCenterStatus
    {
        /// <summary>
        /// 段落居中良好（在容许之内）
        /// </summary>
        WellCentered,
        /// <summary>
        /// 段落未居中
        /// </summary>
        NotCentered,
        /// <summary>
        /// </summary>
        Blank,
        /// <summary>
        /// 段落有多行
        /// </summary>
        MoreThanOneLine,
        /// <summary>
        /// 段落使用了右居中
        /// </summary>
        InvalidAlignment
    }
    /// <summary>
    /// 这个类提供有关段落对齐的函数
    /// </summary>
    public static class DocumentAlignmentHelper
    {
        /// <summary>
        /// 这个类提供有关段落对齐的一次性封装
        /// </summary>
        internal class ParagraphLineAlignmentHelper
        {
            /// <summary>
            /// The constructor
            /// </summary>
            public ParagraphLineAlignmentHelper()
            {
            }
            /// <summary>
            /// the .ctor
            /// </summary>
            /// <param name="indentation"></param>
            /// <param name="Predefinedftsz"></param>
            /// <param name="ParagraphStyleSpecificftsz">the first </param>
            public ParagraphLineAlignmentHelper(Indentation indentation, double ParagraphStyleSpecificftsz)
            {
                LeftIndent = double.Parse(indentation.Left) / 20 * UnitConversion.PointInMilimeter;
                if (indentation.LeftChars != null)
                    LeftCharIndent = double.Parse(indentation.LeftChars.ToString()) / 100 * ParagraphStyleSpecificftsz;
                if (indentation.Hanging != null)
                    hangingIndent = double.Parse(indentation.Hanging) / 20 * UnitConversion.PointInMilimeter;
                if (indentation.HangingChars != null)
                    hangingCharIndent = double.Parse(indentation.HangingChars.ToString()) / 100 * ParagraphStyleSpecificftsz;
                if (indentation.FirstLine != null)
                    firstLineIndent = double.Parse(indentation.FirstLine) / 20 * UnitConversion.PointInMilimeter;
                if (indentation.FirstLineChars != null)
                    firstLineCharIndent = double.Parse(indentation.FirstLineChars.ToString()) / 100 * ParagraphStyleSpecificftsz;
            }
            /// <summary>
            /// represents the starting spaces' length,converted to milimeters
            /// </summary>
            /// <remarks>This field takes tabs and spaces into account</remarks>
            public double startingSpaceEquavilantLength;
            /// <summary>
            /// represents the trailing spaces' length, converted to milimeters
            /// </summary>
            /// <remarks>This field takes tabs and spaces into account</remarks>
            public double endingSpaceEquavilantLength;
            /// <summary>
            /// 左缩进
            /// </summary>
            public double LeftIndent;
            /// <summary>
            /// 左缩进字符
            /// </summary>
            public double LeftCharIndent = -1;
            /// <summary>
            /// 右缩进
            /// </summary>
            public double RightIndent;
            /// <summary>
            /// 右缩进字符
            /// </summary>
            public double RightCharIndent = -1;
            /// <summary>
            /// 首行悬垂
            /// </summary>
            public double hangingIndent;
            /// <summary>
            /// 首行缩进
            /// </summary>
            public double firstLineIndent;
            /// <summary>
            /// 首行悬垂缩进
            /// </summary>
            public double hangingCharIndent = -1;
            /// <summary>
            /// 首行缩进
            /// </summary>
            public double firstLineCharIndent = -1;
            /// <summary>
            /// 该段落中的首行文字长度
            /// </summary>
            public double charactersEquavilantLength;
            /// <summary>
            /// 段落对齐方式
            /// </summary>
            public JustificationValues justificationEnum;
            public double LastStartingMargin;
            public double LastEndingMargin;
            /// <summary>
            /// 有效纸张宽度 = 纸张宽度减页边距*2
            /// </summary>
            internal double EffectivePaperMarginWidth;
            internal void AddStartingSpaceLength(double fontSize, int consecutiveSpaceLength, double margin)
            {
                double FontSizeMilimeter = (fontSize / 9 + margin) * 0.35;
                startingSpaceEquavilantLength += FontSizeMilimeter * (consecutiveSpaceLength - 1);
            }
            internal void AddEndingSpaceLength(double fontSize, int consecutiveSpaceLength, double margin)
            {
                double FontSizeMilimeter = (fontSize / 9 + margin) * 0.35;
                endingSpaceEquavilantLength += FontSizeMilimeter * consecutiveSpaceLength;
            }
            internal void AddCharacterLength(double fontSize, int consecutiveCharacters, double margin)
            {
                double FontSizeMilimeter = (fontSize / 9 + margin) * 0.35;
                charactersEquavilantLength += FontSizeMilimeter * consecutiveCharacters;
            }
            internal void SetLeftIndent(double indent)
            {
                LeftIndent = indent;
            }
            internal void SetRightIndent(double indent)
            {
                RightIndent = indent;
            }
            internal void SetHangingIndent(double hangingValue)
            {
                hangingIndent = hangingValue;
            }
            internal void SetFirstLineIndent(double firstLineIndentValue)
            {
                firstLineIndent = firstLineIndentValue;
            }
            internal void SetHangingCharsIndent(double hangingCharValue)
            {
                hangingCharIndent = hangingCharValue;
            }
            internal void SetFirstLineCharsIndent(double firstLineIndentValue)
            {
                firstLineCharIndent = firstLineIndentValue;
            }
            internal void SetJustificationEnum(JustificationValues je)
            {
                justificationEnum = je;
            }
            /// <summary>
            /// Gets the first line left indent
            /// </summary>
            /// <returns></returns>
            public double GetFirstLineIndent()
            {
                return startingSpaceEquavilantLength + GetEffectiveLeftNonWhiteSpaceLeftIndentForFirstLine();
            }
            /// <summary>
            /// 获得左边距，不考虑首行因素
            /// </summary>
            /// <returns></returns>
            public double GetLeftIndent()
            {
                return GetEffectiveLeftNonWhiteSpaceLeftIndentForOverallAppearance();
            }
            /// <summary>
            /// 获得右边距
            /// </summary>
            /// <returns></returns>
            public double GetRightIndent()
            {
                if (RightCharIndent != -1)
                {
                    return RightCharIndent;
                }
                return RightIndent;
            }
            /// <summary>
            /// Gets the first-line left indent ignoring starting white spaces, only caring about indentation values.
            /// </summary>
            /// <returns></returns>
            public double GetEffectiveLeftNonWhiteSpaceLeftIndentForFirstLine()
            {
                double result = 0;
                if (firstLineCharIndent != -1)
                {
                    result += firstLineCharIndent;
                }
                if (firstLineIndent != 0 && firstLineCharIndent == -1)
                {
                    result += firstLineIndent;
                }
                if (LeftCharIndent != -1)
                {
                    result += LeftCharIndent;
                    return result;
                }
                if (LeftIndent != 0)
                {
                    result += LeftIndent;
                }
                return result;
            }
            /// <summary>
            /// Gets the Left-indent overally for the paragraph
            /// </summary>
            /// <returns></returns>
            public double GetEffectiveLeftNonWhiteSpaceLeftIndentForOverallAppearance()
            {
                double result = 0;
                if (LeftCharIndent != 0)
                {
                    result += LeftCharIndent;
                }
                if (LeftIndent != 0 && LeftCharIndent == 0)
                {
                    result += LeftIndent;
                }
                if (hangingCharIndent != 0)
                {
                    result += hangingCharIndent;
                }
                if (hangingIndent != 0 && hangingCharIndent == 0)
                {
                    result += hangingIndent;
                }
                return result;
            }
        }
        public static void QParagraphEffectiveLeftIndentationInMilimeterDualValue(Paragraph para, WordprocessingDocument wd, out double firstLine, out double nonFirstLine, double paperWidth = 210-63.6, HostApplications ha = HostApplications.MsOffice2010)
        {
            _QParagraphEffectiveLeftIndentationInMilimeter(para, wd, out firstLine, out nonFirstLine, paperWidth, ha);
        }
        private static void _QParagraphEffectiveLeftIndentationInMilimeter(Paragraph para, WordprocessingDocument wd, out double firstLine, out double nonFirstLine, double paperWidth = 210-63.6, HostApplications ha = HostApplications.MsOffice2010)
        {
            firstLine = nonFirstLine = 0;
            if (para.InnerText.Trim() == "") return;
            DefaultTabStop dts = null;
            double automaticTabLength = 0;

            dts = DocumentSpecificDefault.GetDefaultOrSettingByPropertyName("DefaultTabStop", typeof(DefaultTabStop), wd) as DefaultTabStop;
            automaticTabLength = (dts.Val / 20) * UnitConversion.PointInMilimeter;

            TabStop[] tabCharLocations = new TabStop[0];
            ///Microsoft Office 2010 Predefined
            double ParagraphStyleSpecificftsz = double.Parse(DocumentSpecificDefault.GetDefaultOrSettingByPropertyName("FontSize", typeof(FontSize), wd, ha).Val) / 2 * UnitConversion.PointInMilimeter;
            DocumentAlignmentHelper.ParagraphLineAlignmentHelper plah;
            Indentation indentation = new Indentation();
            indentation = ParagraphStyle.ParagraphIndentation.GetHierachicalParagraphIndentationEx(para, wd, ha);
            if (para.ParagraphProperties != null)
            {
                if (para.ParagraphProperties.Tabs != null)
                {
                    if (para.ParagraphProperties.Tabs.Count() != 0)
                    {
                        tabCharLocations = para.ParagraphProperties.Tabs.ChildElements.Cast<TabStop>().ToArray();
                    }
                }
            }
            double FirstRunDefinedftsz = double.Parse(new RunInfoHelper(wd).GetRunInfo(para.ChildElements.First<Run>() == null ? para.ChildElements.First<Hyperlink>().ChildElements.First<Run>() : para.ChildElements.First<Run>()).FontSize) / 2 * UnitConversion.PointInMilimeter;
            plah = new ParagraphLineAlignmentHelper(indentation, FirstRunDefinedftsz);
            int tabEnumerator = 0;
            //  tabEnumerator.MoveNext();
            bool stopEnumeration = false;
            bool firstTimeNonGridTab = true;
            for (int i = 0; i < para.ChildElements.Count; i++)
            {
                if (stopEnumeration)
                {
                    break;
                }
                if (para.ChildElements[i].LocalName == "r")
                {
                    Run run = para.ChildElements[i] as Run;
                    bool StopInnerEnumeration = false;
                    foreach (OpenXmlElement oxe in run.ChildElements)
                    {
                        if (StopInnerEnumeration)
                        {
                            break;
                        }
                        if (oxe.LocalName == "t")
                        {
                            StopInnerEnumeration = true;
                        }
                        if (oxe.LocalName == "tab")
                        {
                            bool useDefault = false;
                            TabStop tbStop = null;
                            try
                            {
                                tbStop = (TabStop)tabCharLocations[tabEnumerator++];
                                useDefault = false;
                            }
                            catch
                            {
                                useDefault = true;
                            }

                            //do not use default, not using automatic tabing
                            if (!useDefault)
                            {
                                try
                                {
                                    bool isLeft = tbStop.Position.HasValue ? tbStop.Position.Value == 1 : false;
                                    double tbIndent = tbStop.Position.Value / UnitConversion.TwillpToMilimeter;
                                    //
                                    //the logic behind tab is once Position is set, the left indent is overriden by the position value.
                                    //
                                    if (isLeft)
                                    {
                                        plah.startingSpaceEquavilantLength = tbIndent;
                                    }
                                    else
                                    {
                                        plah.startingSpaceEquavilantLength = paperWidth - tbIndent;
                                    }
                                }
                                catch (NullReferenceException)
                                {
                                    // plah.startingSpaceEquavilantLength+=
                                }
                            }
                            //use default , automatic tabing
                            else
                            {
                                if (firstTimeNonGridTab)
                                {
                                    plah.startingSpaceEquavilantLength += ParagraphStyleSpecificftsz / 2;
                                    firstTimeNonGridTab = false;
                                }
                                else
                                {
                                    plah.startingSpaceEquavilantLength += automaticTabLength;
                                }
                            }
                        }


                    }
                    if (run.InnerText.Trim() != "")
                    {
                        stopEnumeration = true;
                    }
                    int count = 0;
                    for (int x = 0; x < run.InnerText.Length; x++)
                    {
                        if (run.InnerText[x] == ' ')
                        {
                            count++;
                        }
                        else
                        {
                            break;
                        }
                    }
                    plah.startingSpaceEquavilantLength += count * ParagraphStyleSpecificftsz / 2;
                }

            }
            nonFirstLine = plah.GetLeftIndent();
            firstLine = plah.GetFirstLineIndent();


        }
        /// <summary>
        /// 智能*:获得段落左缩进，综合考虑所有因素
        /// </summary>
        /// <param name="para"></param>
        /// <param name="wd"></param>
        /// <param name="paperWidth"></param>
        /// <param name="firstLine">是否为首行缩进</param>
        /// <param name="ha"></param>
        /// <returns>左缩进，以毫米计算</returns>
        public static double QParagraphEffectiveLeftIndentationInMilimeter(Paragraph para, WordprocessingDocument wd, double paperWidth = 210-63.6, bool firstLine = false, HostApplications ha = HostApplications.MsOffice2010)
        {
            double fl;
            double nonfl;
            _QParagraphEffectiveLeftIndentationInMilimeter(para, wd, out fl, out nonfl, paperWidth, ha);
            if (firstLine)
            {
                return fl;
            }
            else
            {
                return nonfl;
            }

        }
        /// <summary>
        /// 智能*:获得段落右缩进，综合考虑所有因素
        /// </summary>
        /// <param name="para"></param>
        /// <param name="wd"></param>
        /// <param name="paperWidth"></param>
        /// <returns>毫米</returns>
        public static double QParagraphEffectiveRightIndentationInMilimeter(Paragraph para, WordprocessingDocument wd, double paperWidth = 210-63.6, HostApplications ha = HostApplications.MsOffice2010)
        {
            if (para.InnerText.Trim() == "") return 0;
            if (para.ParagraphProperties == null)
            {
                para.ParagraphProperties = new ParagraphProperties();
            }
            if (para.ParagraphProperties.Indentation == null)
            {
                para.ParagraphProperties.Indentation = new Indentation();
            }
            double Predefinedftsz = double.Parse(DocumentSpecificDefault.GetDefaultOrSettingByPropertyName("FontSize", typeof(FontSize), wd).Val) / 2 * UnitConversion.PointInMilimeter;
            // RunPropertiesDefault rpd = wd.MainDocumentPart.StyleDefinitionsPart.Styles.DocDefaults.RunPropertiesDefault;
            RunInfo ri = new RunInfoHelper(wd).GetRunInfo(para.ChildElements.First<Run>() == null ? para.ChildElements.First<Hyperlink>().ChildElements.First<Run>() : para.ChildElements.First<Run>());
            DocumentAlignmentHelper.ParagraphLineAlignmentHelper plah;
            double ParagraphStyleSpecificftsz = double.Parse(ri.FontSize) / 2 * UnitConversion.PointInMilimeter;
            Indentation ind = ParagraphStyle.ParagraphPropertiesHelper.QParagraphPropertiesMultiValueProperty<Indentation>(para, wd, ha);
            plah = new ParagraphLineAlignmentHelper(ind, ParagraphStyleSpecificftsz) { EffectivePaperMarginWidth = paperWidth };
            return plah.GetRightIndent();
        }
        /// <summary>
        /// intelliGent::intelligently determines if a paragraph is centered
        /// </summary>
        /// <param name="para">the paragraph to be tested</param>
        /// <param name="wd">the WordProcessingDocument Associated with the paragraph</param>
        /// <param name="toleranceInMilimeter">the tolerance used when judging if a paragraph is centered</param>
        /// <param name="ha">the hostapplication in which the paragraph is written</param>
        /// <param name="effectiveW">the width of the paper</param>
        /// <returns></returns>
        public static ParagraphCenterStatus QParagraphCenterStatusEx(Paragraph para, WordprocessingDocument wd, double toleranceInMilimeter = 0, HostApplications ha = HostApplications.MsOffice2010, double effectiveW = 210-63.6)
        {
            if (para.InnerText.Trim() == "")
            {
                return ParagraphCenterStatus.Blank;
            }
            // double Predefinedftsz = 10.5 * UnitConversion.PointInMilimeter;
            Justification justif = new Justification() { Val = JustificationValues.Left };
            justif = ParagraphStyle.ParagraphPropertiesHelper.QParagraphPropertiesSingleValueProperty("Justification", para, wd, ha);
            if (justif.Val != JustificationValues.Left && justif.Val != JustificationValues.Center && justif.Val != JustificationValues.Both)
            {
                return ParagraphCenterStatus.InvalidAlignment;
            }
            /*      RunPropertiesDefault rpd = wd.MainDocumentPart.StyleDefinitionsPart.Styles.DocDefaults.RunPropertiesDefault;
                  double ParagraphStyleSpecificftsz = 10.5;
                  try
                  {
                      ParagraphStyleSpecificftsz = double.Parse(ParagraphStyle.ParagraphIndentation.GetHierachicalParagraphIndentationByPropertyName("FontSize", para, wd).Val) / 2 * UnitConversion.PointInMilimeter;
                  }
                  catch (ApplicationException)
                  {
                  }
                  if (rpd.RunPropertiesBaseStyle != null)
                  {
                      Predefinedftsz = double.Parse(rpd.RunPropertiesBaseStyle.FontSize.Val) / 2 * UnitConversion.PointInMilimeter;
                  }*/
            double leftIndent = QParagraphEffectiveLeftIndentationInMilimeter(para, wd, effectiveW, true, ha);
            if (justif.Val == JustificationValues.Center)
            {
                if (leftIndent != 0 && toleranceInMilimeter < leftIndent)
                {
                    return ParagraphCenterStatus.NotCentered;
                }
                if (Math.Abs(leftIndent - QParagraphEffectiveRightIndentationInMilimeter(para, wd, effectiveW)) <= toleranceInMilimeter)
                {
                    return ParagraphCenterStatus.WellCentered;
                }
            }

            Run r = null;
            try
            {
                r = para.ChildElements.Where<OpenXmlElement>(o => o.LocalName == "r").Cast<Run>().First<Run>(o =>
                    {
                        return o.InnerText.StartsWith(" ") && o.InnerText.Trim() != "";
                    });
            }
            catch
            {
                r = para.ChildElements.First<Run>();
            }

            //   int countWidth = r.InnerText.GetLastCharacterNotSpaceIndex();
            TabChar[] tabCharLocations = new TabChar[0];
            if (para.ParagraphProperties.Tabs.Count() != 0)
            {
                tabCharLocations = para.ParagraphProperties.Tabs.ChildElements.Cast<TabChar>().ToArray();
            }
            double automaticTabLength = double.Parse(DocumentSpecificDefault.GetDefaultOrSettingByPropertyName("DefaultTabStop", typeof(DefaultTabStop), wd, ha).Val) * UnitConversion.TwillpToMilimeter;
            var tabCharEnumerator = tabCharLocations.GetEnumerator();
            tabCharEnumerator.Reset();
            tabCharEnumerator.MoveNext();
            double charLength = 0;

            charLength = r.GetRunCharacterLengthIgnoringStartingWhiteSpace(wd);
            // double fullIndentationLeft = charLength + leftIndent;
            OpenXmlElement oxe = r;
            bool lst = r.InnerText.Last<char>().IsSyntaxRequringAdditionalSpacingBetweenRun();
            while (oxe.NextSibling() != null)
            {
                oxe = oxe.NextSibling();
                if (oxe.LocalName == "r")
                {
                    Run _r = oxe as Run;
                    bool fast = _r.InnerText.Last<char>().IsSyntaxRequringAdditionalSpacingBetweenRun();
                    if (oxe.NextSibling<Run>() != null)
                    {
                        charLength += _r.GetRunCharacterLengthCountingWhiteSpace(wd, fast != lst);
                    }
                    else
                    {
                        charLength += _r.GetRunCharacterLengthCountingWhiteSpace(wd, false);
                    }
                    lst = fast;
                    if (charLength + leftIndent > effectiveW)
                    {
                        return ParagraphCenterStatus.MoreThanOneLine;
                    }
                }
                if (oxe.LocalName == "tab")
                {
                    TabChar tbChar = tabCharEnumerator.Current as TabChar;
                    bool useDefault;
                    useDefault = tabCharEnumerator.MoveNext();
                    try
                    {
                        //do not use default, not using automatic tabing
                        if (!useDefault)
                        {
                            bool isLeft = tbChar.GetAttributes().Where<OpenXmlAttribute>(o => o.LocalName == "val").First<OpenXmlAttribute>().Value == "left";
                            double tbIndent = double.Parse(tbChar.GetAttributes().Where<OpenXmlAttribute>(o => o.LocalName == "pos").First<OpenXmlAttribute>().Value);
                            ///
                            ///the logic behind tab is once Position is set, the left indent is overriden by the position value.
                            ///
                            if (isLeft)
                            {
                                charLength = tbIndent - leftIndent;
                            }
                            else
                            {
                                charLength = effectiveW - tbIndent - leftIndent;
                            }
                        }
                        //use default , automatic tabing
                        else
                        {
                            int ratio = (int)(charLength + leftIndent / automaticTabLength);
                            charLength = (ratio + 1) * automaticTabLength;
                        }
                        if (charLength + leftIndent > effectiveW)
                        {
                            return ParagraphCenterStatus.MoreThanOneLine;
                        }
                    }
                    catch (NullReferenceException)
                    {
                        // plah.startingSpaceEquavilantLength+=
                    }
                }

            }
            double _rightMargin = effectiveW - charLength - leftIndent;
            if (Math.Abs(leftIndent - _rightMargin) > toleranceInMilimeter)
            {
                return ParagraphCenterStatus.NotCentered;
            }
            else
            {
                return ParagraphCenterStatus.WellCentered;
            }
        }
    }

    class _InitializationUtilities
    {
        /// <summary>
        /// Set bjects to their defaults intelligently.
        /// 仅初始化一层。
        /// </summary>
        /// <param name="o"></param>
        /// <param name="types"></param>
        [Obsolete("this method is no longer available, try InitializationService.GetObjectInitialized instead", true)]
        public static void InitializeComponentsByObjectsAndTypes(object[] o, Type[] types)
        {
            for (int x = 0; x < o.Length; x++)
            {
                if (o[x] == null)
                {
                    o[x] = Activator.CreateInstance(types[x]);
                }
                foreach (PropertyInfo pi in o[x].GetType().GetProperties())
                {
                    if (pi.GetValue(o[x], null) == null)
                    {
                        if (pi.PropertyType == typeof(StringValue))
                        {
                            if (pi.Name == "Val")
                                pi.SetValue(o[x], new StringValue("21"), null);
                            else if (pi.Name != "LeftChars" && pi.Name != "RightChars")
                                pi.SetValue(o[x], new StringValue("0"), null);
                        }
                        if (pi.PropertyType == typeof(Int32Value))
                        {
                            pi.SetValue(o[x], new Int32Value(0), null);
                        }
                        /*if (pi.PropertyType == typeof(int) || pi.PropertyType == typeof(double))
                         {
                             pi.SetValue(i, 0, null);
                         }*/
                    }
                }
            }
        }
    }

}






